<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document 2022</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    article {
      padding: 10px;
    }
    div {
      border: 1px solid #ccc;
      margin-bottom: 10px;
      padding: 10px;
    }
    header {
      font-size: 20px;
      font-weight: 600;
      color: #f00;
      margin-bottom: 10px 0;
    }
    h3 {
      font-size: 15px;
      /* margin-bottom: 3px; */
      margin-top: 10px;
      word-break: break-all;
    }
    li {
      list-style: none;
    }
    pre {
      background: #eee;
      padding: 10px;
      /* width: 100%; */
      overflow-x: auto;
    }
    section {
      font-size: 13px;
      color: #666;
    }
    a {
      word-break: break-all;
    }
  </style>
</head>
<body>
  <article id='app'></article>
  <script>
    var contents = [
      {
        title: 'CSS',
        children: [
          {
            title: '盒模型',
            content: '标准盒模型-属性width、height只包含content，不包含border和padding；ie盒模型-包含content、border和padding；border-box、content-box'
          },
          {
            title: '伪类与伪元素的区别',
            content: '1、伪类用于定义元素的特殊状态，如悬停、点击、索引等；伪元素表示元素指定部分，如元素前、元素后、元素首字母等。2、伪类用于为了设置元素不同状态的样式；伪元素主要用于创建抽象元素来达到某种目的'
          },
          {
            title: 'box-sizing',
            content: '定义了盒模型计算高宽的方法'
          },
          {
            title: '移动端适配方案',
            content: '1、百分比；2、媒体查询；3、rem；4、vw vh'
          }
        ]
      },
      {
        title: 'js',
        children: [
          {
            title: 'set,map,weakSet,weakMap的区别',
            content: 'set成员唯一，无序，只需传入value，且value无限制；weakSet成员唯一，但是value仅接收对象、数组、函数等引用类型数据；map键值对集合，且key、value不做限制；而weakMap的key仅接收对象等引用数据；'
          },
          {
            title: '全局作用域中，用 const 和 let 声明的变量不在 window 上，那到底在哪里？如何去获取？',
            content: '用 let 和 const 声明的全局变量并没有在全局对象(global)中，只是一个块级作用域（Script）中。在定义变量的块级作用域中就能获取'
          },
          {
            title: '闭包',
            content: '将函数内部与外部连接起来，函数和函数内部能访问到的（非全局）变量总和。闭包很简单，就是能够访问另一个函数作用域变量的函数，更简单的说，闭包就是函数，只不过是声明在其它函数内部而已。'
          },
          {
            title: '闭包的应用场景',
            content: '函数防抖、节流'
          },
          {
            title: 'valueOf与toString',
            content: 'https://www.cnblogs.com/imwtr/p/4392041.html',
          },
          {
            title: 'setTimeout、Promise、Async/Await 的区别',
            content: '其中settimeout的回调函数放到宏任务队列里，等到执行栈清空以后执行； promise.then里的回调函数会放到相应宏任务的微任务队列里，等宏任务里面的同步代码执行完再执行；async函数表示函数里面可能会有异步方法，await后面跟一个表达式，async方法执行时，遇到await会立即执行表达式，然后把表达式后面的代码放到微任务队列里，让出执行栈让同步代码先执行。'
          },
          {
            title: '区分数组和对象',
            content: '1、instanceof；2、Array.isArray；3、判断对象constructor是否等于Array；4、判断对象的__proto__是否等于Array的prototype；5、转换为字符串后正则匹配；'
          },
          {
            title: '浏览器的进程与线程',
            content: '每个页面都是一个进程，进程之间互不影响；一个进程包含多个线程，'
          },
          {
            title: 'Symbol的使用场景',
            content: '对象属性；常量；'
          },
          {
            title: 'url参数过长，无法页面跳转',
            content: '1、同源的情况下的，可以通过本地cookie、localStorage、sessionStorage进行数据交互；2、不同源的时候，可以借助服务端存储目标域名下的cookie进行数据交互；3、可以通过接口进行数据缓存； 4、可以借助form表单进行post提交并跳转，action为目标地址；5、使用iframe或window.open打开目标地址，并将参数通过postMessage发送过去；'
          },
          {
            title: '原型与原型链',
            // content: '所有原型链的终点都是Object的prototype属性；每一个构造函数都有一个prototype属性，执行它的原型对象；原型对象都有一个constructor属性，执行它的构造函数；每个对象都有一个__proto__指向它的原型对象；如果一个实例函数的原型对象是另外一个构造函数的实例，则构成了原型链；'
            content: `所有的实例函数都有一个__proto__属性，指向它的原型对象；而所有的原型对象都有一个constructor属性，指向它的构造函数；而每个构造函数都有一个prototype属性，指向它的原型对象。<br/>
            所以实例函数的__proto__和其构造函数的constructor属性指向的是同一个对象，都是构造函数的原型对象。<br/>
            如果一个实例函数的原型对象，是另外一个构造函数的实例，则构成了原型链。
            `
          },
          {
            title: 'js设计模式',
            content: '发布-订阅者模式、工厂模式、原型模式、装饰器模式、享元模式'
          },
          {
            title: '介绍模块化发展历程',
            content: '模块化主要是用来抽离公共代码，隔离作用域，避免变量冲突等。1、AMD，使用requireJS 来编写模块化，特点：依赖必须提前声明好；2、CMD，使用seaJS 来编写模块化，特点：支持动态引入依赖文件；3、CommonJS，nodejs 中自带的模块化；4、UMD，兼容AMD，CommonJS 模块化语法；5、ES Modules，ES6 引入的模块化，支持import 来引入另一个 js；'
          },
          {
            title: '说说浏览器中的事件循环',
            content: '1、关于微任务和宏任务在浏览器的执行顺序是这样的：执行一个宏任务、执行完微任务队列，如此循环下去；'
          },
          {
            title: '宏任务与微任务',
            links: [{
              name: '宏任务与微任务',
              url: 'https://www.jianshu.com/p/a5070f72668b'
            }],
            content: '宿主环境提供的异步方法都是宏任务，如script、UI渲染、定时器等；语言提供的promise、mutationObserver等为微任务；js=>全部微任务=>ui渲染=>一个宏任务=>全部微任务=>ui渲染=>第二个宏任务=>全部微任务=>ui渲染......最后一个宏任务=>全部微任务=>ui渲染=>结束'
          },
          {
            title: '宏任务',
            content: '每次执行栈执行的代码就是一个宏任务，浏览器为了能够使得js内部宏任务与dom任务能够有序的执行，会在一个宏任务执行结束后，在下一个宏任务执行开始前对页面进行重新渲染。宏任务包括：script整体代码、setTimeout、setInterval、交互事件、postMessage（web-worker）等'
          },
          {
            title: '微任务',
            content: '可以理解是在当前任务执行结束后立即执行的任务，在当前宏任务后，下一个宏任务之前且渲染之前执行的任务即为微任务；某一个宏任务结束之后，会将在它执行期间产生的所有微任务执行完毕；包括：Promise.then'
          },
          {
            title: 'Promise',
            content: 'promise是一种异步编程的解决方案吧，其实质是一个构造函数，自身有all、reject、resolve方法，原型上有then、catch等方法；promise构造函数主体是同步执行，但是then处理是异步的。'
          },
          {
            title: 'async/await',
            content: '等待异步函数完成，并处理函数返回的结果。是异步的终极解决方案，将异步改造为同步，处理了回掉地狱的问题'
          },
          {
            title: 'cookie、localStorage等的区别',
            content: '大小：cookie-4K、storage-5M；周期：cookie时效性，localStorage手动清理，sessionStorage页面关闭自动清理；通信：cookie可携带与header与服务端通信'
          },
          {
            title: 'JS 异步解决方案的发展历程以及优缺点',
            content: '1、回调函数，缺乏顺序性，无法trycatch，回调地狱，很大的耦合性；2、Promise，有resolve和reject、finally，但是无法取消Promise；3、async/await，异步的终极解决方案，代码清晰，不用像promise写一堆then，也不用陷入回调地狱，但是大量的异步改为同步，可能会影响性能；',
          },
          {
            title: 'js性能优化的方法',
            content: '1、合理使用防抖与节流；2、数组dom事件监听使用事件委托；'
          }
        ]
      },
      {
        title: 'ts',
        children: [
          {
            title: 'implements 与 extends 类接口 的区别',
            content: 'extends：顾名思义，继承，一个新的接口或者类，从父类或者接口继承所有的属性和方法，不可以重写属性，但可以重写方法；implements：顾名思义，实现，一个新的类，从父类或者接口实现所有的属性和方法，同时可以重写属性和方法，包含一些新的功能;'
          },
          {
            title: 'Never类型有什么用',
            content: 'never类型表示的是那些永不存在的值的类型,never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型； 变量也可能是 never类型，当它们被永不为真的类型保护所约束时。never的作用是可以确保条件收窄不会遗漏，如有遗漏会出现类型报错'
          },
          {
            title: 'TypeScript 中 any、never、unknown 和 void 有什么区别？',
            content: 'any ：用于描述任意类型的变量，不作任何约束，编译时会跳过对其的类型检查；unknown ：表示未知类型，即写代码的时候还不知道具体会是怎样的数据类型；never ：永不存在的值的类型，常用于表示永不能执行到终点的函数返回值，例如抛出异常或函数中执行无限循环的代码（死循环）的函数返回值类型；void ：表示无任何类型，没有类型，例如没有返回值的函数的返回值类型；'
          },
          {
            title: 'TypeScript 中 Interface 与 Type 的区别？',
            content: 'interface 只能定义对象类型。type声明可以声明任何类型。'
          },
          {
            title: 'ts中const与readonly的区别？',
            content: 'const 用于常量， readonly 用于属性；const 在运行时检查， readonly 在编译时检查；'
          },
          {
            title: 'ts的优势在哪里？为什么要用ts？',
            content: '1、代码检查；2、类型限制-类型就是最好的注释、增加了代码的可读性和可维护性；3、引入接口、类、继承的编程思想；4、TypeScript 增强了编辑器（IDE）的功能，提供了代码补全、接口提示、跳转到定义、代码重构等能力'
          }
        ]
      },
      {
        title: 'vue',
        children: [
          {
            title: 'vue双向绑定原理',
            content: '通过发布-订阅则模式完成：监听器observer，劫持并监听属性，属性变化，则通知订阅者；订阅器dep，用来收集订阅者，对监听器observer和订阅者watcher进行统一管理；订阅者watcher，收集属性的变化，通知并执行相应的方法更新视图；解析器compile，渲染DOM；'
          },
          {
            title: 'v-if,v-show,v-html的原理',
            content: 'v-if会调用addIfCondition方法，生成vnode的时候会忽略对应节点，render的时候就不会渲染；v-show会生成vnode，render的时候也会渲染成真实节点，只是在render过程中会在节点的属性中修改show属性值，也就是常说的display；v-html会先移除节点下的所有节点，调用html方法，通过addProp添加innerHTML属性，归根结底还是设置innerHTML为v-html的值;'
          },
          {
            title: 'computed与watch的区别',
            content: 'watch监听一个实例上的变量的变化，可以接受2个参数（newValue, oldValue）,即变化的最新值和上一次变化的旧值；computed是计算属性，即示例上的数据不改变会有缓存，当下一次遇到的属性如果之前使用过，就会把缓存中的值放上去；'
          },
          {
            title: 'computed怎么实现的',
            content: 'computed本身是通过代理的方式代理到组件实例上的，所以读取计算属性的时候，执行的是一个内部的getter，而不是用户定义的方法。computed内部实现了一个惰性的watcher，在实例化的时候不会去求值，其内部通过dirty属性标记计算属性是否需要重新求值。当computed依赖的任一状态（不一定是return中的）发生变化，都会通知这个惰性watcher，让它把dirty属性设置为true。所以，当再次读取这个计算属性的时候，就会重新去求值。'
          },
          {
            title: '谈一谈 nextTick 的原理',
            content: '目前浏览器平台没有实现nextTick，vue根据兼容性分别用promise setTimeout setImmediate等方式创建一个宏任务/微任务（若支持promise则使用promise创建一个微任务，否则用setImmediate、setTimeout创建一个红任务，因为setImmediate的延迟是小于setTimeout的），目的就是当前执行栈代码执行完毕以后再执行。所以，即使js有大量的数据修改，都会执行完成之后，再视图更新，而nextTick里传入的回调函数也会在更新完成之后执行。'
          },
          {
            title: 'vue自定义指令原理',
            content: '指令本质是通过生命周期对html扩展的装饰器。给html元素增加自定义功能，vue编译dom时候，会执行指令关联的js代码，即找到指令对象，并执行相关方法。'
          },
          {
            title: 'vue2.x不能检测的变化',
            content: '下标改数组元素；使用赋值方式改变数组长度；使用下标删除数组元素'
          },
          {
            title: '异步更新队列',
            content: 'Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化，Vue 将开启一个队列，并缓冲在同一事件循环中发生的所有数据变更。如果同一个侦听器被多次触发，它只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后，在下一个的事件循环“tick”中，Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate，如果执行环境不支持，则会采用 setTimeout(fn, 0) 代替。'
          },
          {
            title: 'vue3.x中的proxy',
            content: 'Proxy 是一个对象，它包装了另一个对象，并允许你拦截对该对象的任何交互。可以直接监听对象而非属性；可以直接监听数组的变化；proxy返回的是一个新对象，而Object.defineProperty只能遍历对象属性进行修改'
          },
          {
            title: 'vue3.x中的组合式api（Composition api）',
            content: '它是仿照react的hooks写的；所有逻辑都写在setup函数中，可以提高代码的复用性；setup接受两个参数props和context(用来访问vue实例上下文)；setup中无法使用this，因为它发生在其它组件选项之前被调用的；使用ref和reactive创建响应式数据；可以使用watch、computed以及生命周期等；'
          },
          {
            title: 'vue3.x中为什么无法使用this',
            content: '因为setup是在其它组件选项之前被调用的，setup内部的this行为与其他选项中的this完全不同，这使得 setup() 在和其它选项式 API 一起使用时可能会导致混淆。'
          },
          {
            title: 'vue3的新特性',
            content: '1、组合式api；2、Teleport；3、片段--vue3支持多根节点的组件；4、自定义事件需要在子组件中emits选项列出；5、单文件组件（<script setup>）语法糖；6、style标签方面的变化（可以引入组件中的变量）；7、Suspense组件，可用于懒加载；8、移除了过滤器filter,$children,$destory,$set等方法；9、生命周期的变动destroy改为了UNmount'
          },
          {
            title: 'watch与watchEffect的区别',
            content: '1、正如react的useEffect一样，watchEffect会立即执行一次传入的函数；2、watchEffect函数的执行仅依赖于函数内部使用的变量，而watch可以传入多个数据源；3、watchEffect返回一个中止函数，运行则会停止监听，而watch则没有；4、watch可以拿到新旧数据值；'
          },
          {
            title: 'Vue3.0 采用了 Proxy，抛弃了 Object.defineProperty,有什么好处？',
            content: 'Object.defineProperty无法监控到数组下标的变化，导致通过数组下标添加元素，不能实时响应；Object.defineProperty只能劫持对象的属性，从而需要对每个对象，每个属性进行遍历，如果，属性值是对象，还需要深度遍历,Proxy可以劫持整个对象，并返回一个新的对象；Proxy不仅可以代理对象，还可以代理数组，还可以代理动态增加属性；defineProperty是递归遍历对象进行属性劫持，性能开销比较大，而proxy是懒劫持整个对象，性能好一些；'
          },
          {
            title: 'createApp',
            content: '返回一个提供应用上下文的应用实例，应用实例挂载的整个组件树共享同一个上下文。'
          },
          {
            title: '双向绑定和 vuex 是否冲突',
            content: 'v-model绑定vuex数据的时候，严格模式下会抛出错误，可以通过computed的setter和getter来解决，也可以通过:value和change来解决'
          },
          {
            title: '子组件为何不可以修改父组件传递的 Prop',
            content: '单向数据流，易于监测数据的流动，出现了错误可以更加迅速的定位到错误发生的位置;一个父组件下不只有你一个子组件,使用这份 prop 数据的也不只有你一个子组件,如果每个子组件都能修改 prop 的话，将会导致修改数据的源头不止一处;'
          },
          {
            title: 'vue的性能优化',
            content: '1、v-for的key唯一性且不是index；2、v-if不与v-for同用，v-if比v-for的优先级高 会先执行循环，同时使用了会每次循环都执行一次v-if，影响性能；3、路由的代码切割，按需加载；4、v-once标记不需要检测更新视图的组件；5、适当的使用v-memo；6、v-if与v-show合理的使用；7、路由中使用keep-alive；8、如果列表内容过多，仅渲染可视化部分；9、对于简单组件使用functional变为函数型组件；'
          }
        ]
      },
      {
        title: 'react',
        children: [
          {
            title: 'setState之后，无法立即获取最新的值',
            content: 'setState更新状态是异步的，由React控制的事件处理程序，以及生命周期函数调用setState不会同步更新state 。React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件，setTimeout/setInterval等。'
          },
          {
            title: 'React的更新机制',
            content: `React的setState本身并不是异步的，是因为其批处理机制给人一种异步的假象。<br>
            <strong>生命周期函数和合成事件中：</strong>1.无论调用多少次setState，都不会立即执行更新。而是将要更新的state存入'_pendingStateQuene',将要更新的组件存入'dirtyComponent';2.当根组件didMount后，批处理机制更新为false。此时再取出'_pendingStateQuene'和'dirtyComponent'中的state和组件进行合并更新；<br/>
            <strong>原生事件和异步代码中：</strong>1.原生事件不会触发react的批处理机制，因而调用setState会直接更新；2.异步代码中调用setState，由于js的异步处理机制，异步代码会暂存，等待同步代码执行完毕再执行，此时react的批处理机制已经结束，因而直接更新；<br>
            <strong>总结：</strong>react会表现出同步和异步的现象，但本质上是同步的，是其批处理机制造成了一种异步的假象。（其实完全可以在开发过程中，在合成事件和生命周期函数里，完全可以将其视为异步;         
            `
          },
          {
            title: '什么是纯组件(pureComponent)',
            content: '它与普通组件一样，只是负责shouldComponentUpdata--对状态和props进行浅比较'
          },
          {
            title: 'React.memo组件',
            content: '它与pureComponet组件类似，只是用于函数式组件，hooks写函数组件的pureComponent组件的替代品。'
          },
          {
            title: 'react渲染原理',
            content: 'babel解析jsx为React.createElemetn的嵌套语法（包含元素名、属性、子元素（createElement嵌套）、键值KEY等），生产虚拟DOM，通过diff算法计算每次重新更新的组件'
          },
          {
            title: '对shouldComponentUpdate的理解',
            content: '根据shouldComponentUpdate返回的值判断是否组件重新渲染受当前state或props值变化的影响。当props或state变化的时候，会触发shouldComponentUpdate钩子，首次渲染和调用forceupdate的时候不会触发。'
          },
          {
            title: 'component与pureComponent的不同',
            content: '两者的区别在于 React.Component 并未实现 shouldComponentUpdate()，而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。'
          },
          {
            title: 'React hooks useEffect实现componentDidMount效果',
            content: 'useEffect接受两个参数，将第二个参数设为空数组，表示这个effect只会在componentDidMount和componentWillUnMount的时候调用，即可实现componentDidMount效果。'
          },
          {
            title: 'useState',
            content: 'useState是一个状态hook，在函数内部添加一些内部state，函数重复渲染的时候会保留，下一次重复渲染的时候useState会返回给当前的state值（所以不叫createState而是useState）。它返回一对值：当前state和修改它的方法。它的方法类似class组件的setState，但是不同的是它不会把新旧state合并而是覆盖。useState更新state是异步的，所以想要监视state的值可以使用useEffect解决。'
          },
          {
            title: 'useEffect',
            content: 'useEffect相当于class组件的componentDidMount、componentDidUpdate、componentWillUnMount组合。使用这个hook，它高速react组件渲染完【注意是渲染完成】之后要做什么。且会保留这个函数，于组件更新之后调用。useEffect的第一个参数函数可以返回一个清除函数，用来清除副作用，正如class组件每次组件卸载都会调用componentWillUNmount，React会在当前effect执行的时候清除上一个effect（即运行effect的清除函数）。第二个数组参数，可以用来空置effect调用的依赖state。'
          },
          {
            title: 'useContext',
            content: 'useContext接收一个context对象（React.createContext创建的值），返回这个context的值。当组件上层最近的<Context.Provider>发生变化的时候，该 Hook 会触发重渲染，并使用最新传递给 MyContext provider 的 context value 值。'
          },
          {
            title: 'useReducer',
            content: 'useReducer接收两个参数（reducer--类似redux的state函数；initialState--初始state；），或则三个参数（此时initState为返回初始值的函数和其参数加起来），返回当前的state和dispatch方法。'
          },
          {
            title: 'useCallback',
            content: '返回一个缓存的回调函数，这个函数只有当useCallback接受的第二个参数变化时候才会发生变化。当你把回调函数传递给经过优化的并使用引用相等性去判断是否重新渲染的子组件的时候，非常有用。'
          },
          {
            title: 'useMemo',
            content: '返回一个缓存值（这个值也可以是回调函数，这个时候就和useCallback异曲同工之妙）。它接受第二个参数（数组），当参数变化（或没有参数时候重新渲染时【！！！】）都会重新计算useMemo的返回值，有点像vue的computed。'
          },
          {
            title: 'useRef',
            content: 'useRef返回一个可变的ref对象，返回的 ref 对象在组件的整个生命周期内保持不变，其current属性值被初始化为useRef接受的参数。返回的ref对象在组件的整个生命周期保持不变。'
          },
          {
            title: 'useImperativeHandle',
            content: 'useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下，应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用'
          },
          {
            title: '其他hooks',
            content: 'useLayoutEffect---与useEffect相同，但它会在所有的DOM变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染；useDebugValue---useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签；'
          },
          {
            title: 'useMemo与useCallback的区别',
            content: 'useMemo返回的值比较广泛，可以是对象、字符串、函数都行；useCallback返回函数。当useMemo返回函数的时候相当于useCallback。'
          },
          {
            title: 'hooks函数式组件如何实现shouldComponentUpdate',
            isCode: true,
            content: `
              // 通过自定义React.memo的第二个参数，如
              const MemoComponent = React.memo(
                  HeaderComponent, 
                  (prevProps, nextProps) => nextProps.count !== prevProps.count
              )
            `
          },
          {
            title: '优化react性能【重要】',
            content: '1、使用纯组件/memo组件；2、shouldComponentUpdate；3、使用React Fragments减少额外标记；4、不要定义使用内联函数（因为每次render都会重新实例绑定新函数，且销毁上一次的老函数，大大增加了react的工作量）；5、函数this的绑定最好在constructor完成（减少每次函数绑定上下文的开销）；6、尽量少的使用内联样式（每次render都会重新转换样式对象）；7、条件渲染要以尽量少的组件显隐为原则；8、render方法中禁止setState；9、数组渲染组件的key唯一性；10、适当的使用useMemo和useCallback缓存数据、函数；'
          }
        ]
      },
      {
        title: '性能',
        children: [
          {
            title: '防抖与节流',
            content: '节流-每隔一段时间触发一次；防抖-以最近一次的触发为准延迟一定时间去执行'
          },
          {
            title: '重绘与重排/回流',
            content: '重绘负责元素的样式更新，回流是重新计算dom树、元素的宽高位置、重新渲染页面；回流必定会触发重绘，重绘不一定会触发回流。重绘的开销较小，回流的代价较高；回流是影响浏览器性能的关键因素，一个元素的回流，可能会导致了其所有子元素以及紧随其后的节点、或祖先节点元素，或部分页面或整个页面的回流；'
          },
          {
            title: '触发页面回流的属性',
            content: 'display 尺寸相关 位置相关 边框相关 padding相关 浮动相关；dom元素的移动 删除 新增操作'
          },
          {
            title: '那些情况会引起内存泄露',
            content: '1、对象相互引用（用完之后将引用设为null）；2、闭包；3、不必要的全局变量；4、没有清理的dom元素引用；5、定时器；6、无终止的递归；7、死循环；'
          },
          {
            title: '如何计算白屏时间和首屏时间，说说你的思路',
            content: '白屏时间=页面开始展示的时间点-开始请求时间点。页面开始展示时间可以通过performance.timing.navigationStart取到，开始请求时间，则可以通过自己打印时间搓。'
          },
          {
            title: '优化项目',
            content: 'cdn存放资源；合理使用css选择器；使用css3动画替代js动画；web服务器启用gzip压缩；使用web-workder；适当使用防抖、节流；使用事件委托；减少不必要的重绘和回流；减少HTTP请求；'
          },
          {
            title: "a.b.c.d 和 a['b']['c']['d']，哪个性能更高",
            content: "a.b.c.d 比 a['b']['c']['d'] 性能高点，因为[ ]里面有可能是字符串，有可能是变量，至少多一次判断，而a.b.c.d是直接取用该字符串当作属性名的"
          }
        ]
      },
      {
        title: '网络',
        children: [
          {
            title: '浏览器渲染',
            content: '重定向-查看缓存-dns解析-tcp链接-发送报文请求-响应报文数据-浏览器解析数据-渲染'          
          },
          {
            title: '浏览器缓存读取规则',
            imgs: [{
              src: './assets/cookie.webp',
              width: '80%'
            }],
            links: [{
              name: '深入理解浏览器的缓存机制',
              url: 'https://www.jianshu.com/p/54cc04190252'
            }],
            content: `
              1、缓存位置：ServiceWorker--运行在浏览器背后的独立线程，一般可用来实现缓存功能;Memory Cache--内存中的缓存，主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等,读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效，可是缓存持续性很短，会随着进程的释放而释放,一旦我们关闭 Tab 页面，内存中的缓存也就被释放了;Disk Cache--磁盘缓存，读取速度慢点，但是什么都能存储到磁盘中，比之 Memory Cache 胜在容量和存储时效性上；Push Cache--推送缓存，是 HTTP/2 中的内容，当以上三种缓存都没有命中时，它才会被使用。它只在会话（Session）中存在，一旦会话结束就被释放，并且缓存时间也很短暂。<br>
              2、缓存过程分析：浏览器发起HTTP请求 – 服务器响应该请求，那么浏览器怎么确定一个资源该不该缓存，如何去缓存呢？浏览器第一次向服务器发起该请求后拿到请求结果后，将请求结果和缓存标识存入浏览器缓存，浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。<br/><img src="./assets/cookie-gc.webp" width='70%' /><br/>
              3、实际场景应用缓存策略：频繁变动的资源--Cache-Control: no-cache；不常变化的资源--Cache-Control: max-age=31536000（一年）。<br>
              4、强缓存：Expires--缓存过期时间；Cache-Control--缓存规则；<br/><img src="./assets/cookie-control.webp" width='60%'><br/>Expires和Cache-Control两者对比：Expires 是http1.0的产物，Cache-Control是http1.1的产物，两者同时存在的话，Cache-Control优先级高于Expires；在某些不支持HTTP1.1的环境下，Expires就会发挥用处。所以Expires其实是过时的产物，现阶段它的存在只是一种兼容性的写法。强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段，而不关心服务器端文件是否已经更新，这可能会导致加载文件不是服务器端最新的内容，那我们如何获知服务器端内容是否已经发生了更新呢？此时我们需要用到协商缓存策略。
              5、协商缓存：协商缓存就是强制缓存失效后，浏览器携带缓存标识向服务器发起请求，由服务器根据缓存标识决定是否使用缓存的过程。
            `
          },
          {
            title: '浏览器渲染原理',
            // content: '浏览器先解析HTML产生一个 DOM Tree;解析 CSS 会产生 CSS Rule Tree;解析完成后，浏览器引擎会通过 DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree;浏览器调用绘制API来渲染出页面;'
            content: '渲染进程解析 HTML 内容转换为能够读懂的 DOM 树结构；解析css，将其和dom结合成具有样式的渲染树；渲染树构建好了之后，执行布局过程，确定每个节点在屏幕上的位置和尺寸等；浏览器绘制每个节点渲染出页面；'
          },
          {
            title: 'http状态码',
            content: '200-ok；301-永久重定向；302-临时重定向；400-报文语法错误；401-用户认证失败；404-not found；500-服务器故障；'
          },
          {
            title: 'TCP 的三次握手（建立链接）',
            content: '第一次：客户端向服务端发送请求报文；第二次：服务端收到链接请求报文之后，若同意，则发送一个应答；第三次：当客户端收到连接同意的应答后，还要向服务端发送一个确认报文，客户端发完这个报文段后便进入 ESTABLISHED 状态，服务端收到这个应答后也进入 ESTABLISHED 状态，此时连接建立成功。'
          },
          {
            title: 'TCP 四次挥手（断开链接）',
            content: '第一次：客户端向服务端发送断开链接请求；第二次：服务端收到请求，会释放链接，不再接受客户端的数据；第三次：服务端向客户端发送完数据之后，发送释放请求；第四次：客户端收到请求之后，是否tcp，断开链接；'
          },
          {
            title: 'HTTP 和 HTTPS 的区别',
            content: 'HTTP 是超文本传输协议，信息是明文传输，HTTPS 则是具有安全性的TLS（SSL）加密传输协议；HTTP 和 HTTPS 使用的是完全不同的连接方式，用的端口也不一样，前者是80，后者是443；HTTP 的连接很简单，是无状态的；，HTTPS协议是由 HTTP+SSL/TLS 协议构建的可进行加密传输、身份认证的网络协议，比 HTTP 协议安全；'
          },
          {
            title: 'http2.0',
            content: 'http2.0是一种安全高效的下一代传输协议，安全是因为它建立在HTTPS基础上的、高效是因为它通过二进制分帧进行数据传输。'
          },
          {
            title: 'http缓存',
            content: `
            http缓存指的是：当客户端请求资源，会先抵达浏览器缓存，如果浏览器有“要请求资源”的副本，就可以直接从浏览器缓存中读取而不需要从服务器中提取。<br/>
            http缓存的分类：根据是否需要向服务器请求分为强制缓存、协商缓存，根据能否被单个/多个用户共用可分为私有缓存、共享缓存
            `
          },
          {
            title: 'https原理',
            content: `
            http协议传输数据是明文的，而HTTPS是经过加密的。<br/>
            验证流程：1、客户端发起HTTPS请求，把自身的Clipher suite（秘钥算法套件）发送给服务端；2、服务端接收clipher与自身支持的对比，若不支持则断开，支持则响应客户端，并发送证书；3、客户端收到响应后做几件事情：验证证书合法性、生产随机密码、hash握手信息并一起发送给服务端；4、服务端拿到客户端传输的密文，用自己的私钥解密握手信息，并得到随机密码，再用随机密码解密握手信息和hash值，与传输来的hash做对比，然后用随机密码加密一段握手信息给客户端；5、客户端用私钥解密握手信息并计算hash，与得到的hash做对比，若一致，则握手结束。`
          },
          {
            title: 'tcp/ip协议',
            content: '指的是不同的通信协议大集合。1、TCP：传输控制协议，负责从应用程序到网络的数据传输控制；2、IP：网际协议，负责计算机之间的通信；3、HTTP：超文本传输协议，负责web浏览器与web服务器之间的通信；4、HTTPS：加密超文本传输协议，加了密的客户端与服务端之间的通信；5、'
          },
          {
            title: '常见的网络攻击',
            content: '1、ddos攻击：向服务器反复发送请求，使其瘫痪；2、sql注入：通过url将关键sql语句带入程序，在数据库中进行破坏；3、木马植入：向服务器植入木马，从而获取服务器的控制权；4、抓包数据获取账号密码；5、XXS攻击：表单提交没有对数据做校验保存了可执行代码(scrip)，要求前后端对数据做校验；6、csrf 跨站请求伪造攻击：简单理解为：攻击者盗用了你的身份，以你的名义发送恶意请求，比如实现转账到 xxx 账户，你登录某一银行网站，进行了转账操作，你本地生成了对应的 cookie 值，恶意攻击者然后诱导你登录恶意网站，悄然利用你的 cookie 给银行网站进行发送转账到他的账户。防护-请求头添加token；'
          },
          {
            title: '前端可以做的安全防护',
            content: '1、表单数据校验；2、生产代码去除注释；3、请求头添加token；4、重要接口不缓存；5、本地存储不存储敏感数据；6、用户名和密码加密操作；7、代码混淆压缩；'
          }
        ]
      },
      {
        title: '框架对比',
        children: [
          {
            title: '什么是虚拟dom',
            content: '虚拟dom的本质是js对象，用来描述dom的结构。应用的状态发生变化，首先作用于虚拟dom，计算出需要修改的实际dom，最总映射到dom。'
          },
          {
            title: 'vue与react有什么不同',
            content: '1、设计思想不同（vue是为了开发的简单，react更在乎开发的方式是否正确）；2、组件扩展（vue主要是mixin，react主要是高阶组件）；3、对数据更新的管理方式不同（vue事件订阅机制，react是通过触发函数调用更新视图）；4、视图重新渲染不同（vue订阅机制对比较那些数据发生了变化会引起那些组件视图的更新，而react setState之后不在乎数据发生了哪些变化，而是粗暴的根据shouldComponentUpdate钩子返回值判断视图是否更新，所以react需要开发者进行性能优化）；总之，react的性能需要手动去做，而vue则是自动的，但是当vue项目中的watcher越来越多的时候，会导致卡顿，所以大型项目react更合适；'
          },
          {
            title: 'vue2.x与vue3.x的区别',
            content: '1、项目入口为createAPP创建实例；2、vue3里面没有了全局vue对象，全局的指令、组件都通过实例APP注册；3、vue3里面去掉了过滤器；4、对数据的懒观察，2.x在实例化的时候对每个数据进行观察，3.x只会对影像视图更新的数据观察，且3.x使用proxy；5、3.x新增了组合式api，即setup钩子；6、3.x的视图更新不再以组件为单位遍历，而是遍历动态内容是否发生了改变；7、vue的功能模块按需引入，使得打包后的体积更小；8、vue3对ts的支持更友好；9、新增了Teleport组件'
          },
          {
            title: 'react、vue2.x、vue3.x diff算法对比（https://blog.csdn.net/JDDTechTalk/article/details/112860897）',
            content: `react的思路是【递增法】，通过对比新的列表中的节点，在原本列表中的位置是否递增来判断当前节点是否需要移动。定义一个变量lastIndex记录上一个节点的位置，遍历新列表。找出节点是否存在于就列表，若存在，则找到每一个节点在旧列表中的位置，若当前位置大于lastIndex，则不需要移动并赋值lastIndex为当前位置，否则，需要移动当前节点；若不存在，则插入新节点；再遍历一次就列表，如果在新列表中找不到，则删之。<br>
            vue2.x的思路则是双端比较。即新列表和旧列表头尾互相比较。在对比的过程中，这真逐渐内靠，直至某一个列表的节点全部遍历完成。定义两个变量，oldStartIndex记录从头部遍历的位置、oldEndIndex记录从尾部遍历的位置。<br>
            vue3.x的
            `
          },
          {
            title: '为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作',
            content: '因为异步操作不可预测，有可能抛异常'
          },
          {
            title: 'Virtual DOM 真的比操作原生 DOM 快吗？谈谈你的想法。',
            content: ''
          }
        ]
      },
      {
        title: '算法题',
        children: [
          {
            title: '将数组arr =[[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]扁平化、去重且排序',
            isCode: true,
            content: `
              1、[...new Set(arr.toString().split(',').map(Number))].sort((a,b)=>a-b);
              2、Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b)；
            `
          },
          {
            title: '求数组var a = [10,21,0,-7,35,7,9,23,18]非零非负最小值 idx',
            isCode: true,
            content: `
              方法一：
              idx =null;
              for (let i=0; i<a.length; i++) {
                if (a[i]>0 && (!idx || a[i]<a[idx])) {
                  idx=i; console.log(idx,i)
                }
              };
              方法二：
              var v= a.reduce((a,b)=>Math.min(...[a,b].filter(a=>a>0)));
              idx=a.indexOf(v);
            `
          },
          {
            title: "将 '10000000000' 形式的字符串，以每 3 位进行分隔展示 '10.000.000.000'",
            isCode: true,
            content: `
              1、var myNum = Number('10000000000'); //字符串转数字后就可以使用toLocaleString()啦~
              var num2Str = myNum .toLocaleString(); //"10,000,000,000"
              num2Str.replace(/,/g, "."); //"10.000.000.000"

              2、'10000000000'.replace(/\B(?=(\d{3})+(?!\d))/g, '.')
            `
          },
          {
            title: '下面代码中 a 在什么情况下会打印 1？',
            isCode: true,
            content: `
            解法1：利用 toString
            let a = {
              i: 1,
              toString () {
                return a.i++
              }
            }

            if(a == 1 && a == 2 && a == 3) {
              console.log(1);
            }

            解法2：利用 valueOf
            let a = {
              i: 1,
              valueOf () {
                return a.i++
              }
            }

            if(a == 1 && a == 2 && a == 3) {
              console.log(1);
            }

            解法3：数组这个就有点妖了
            var a = [1,2,3];
            a.join = a.shift;
            if(a == 1 && a == 2 && a == 3) {
              console.log(1);
            }

            解法4：Object.defineProperty
            Object.defineProperty(window, 'a', {
                get: function() {
                      return this.value = this.value ? (this.value += 1) : 1;
                }
            });
            if(a == 1 && a == 2 && a == 3) {
              console.log(1);
            }

            `
          }
        ]
      }
    ]
  </script>
  <script>
    const $article = document.createElement('article');

    contents.forEach(o=>{
      const $div = document.createElement('div');
      const $header = document.createElement('header');
      $header.innerHTML = o.title;
      $div.appendChild($header);
      
      const $ul = document.createElement('ul');
      
      o.children.forEach(c=>{
        const $li = document.createElement('li');
        const $h3 = document.createElement('h3');
        $h3.innerHTML = c.title;
        const $section = document.createElement('section');

        

        if (c.isCode) {
          const $pre = document.createElement('pre');
          $pre.innerHTML = c.content;
          $section.append($pre);
        } else {
          $section.innerHTML = c.content;
        }

        $li.appendChild($h3);

        if (c.links) {
          c.links.forEach(l=>{
            const $a = document.createElement('a');
            $a.href=l.url;
            $a.innerText =l.name;
            $li.append($a);   
          })
        }

        if (c.imgs) {
          c.imgs.forEach(m=>{
            const $img = document.createElement('img');
            $img.src =m.src;
            console.log(1,m.width)
            $img.style.width =m.width;
            $img.style.height =m.height;
            // $img.height =m.height;
            $li.append($img)
          })
        }

        $li.appendChild($section);

        $ul.appendChild($li);
      });

      $div.appendChild($ul);
      $article.appendChild($div);
    });

    document.querySelector('#app').innerHTML = $article.innerHTML;

  </script>
</body>
</html>